/*
 *    Copyright 2009-2022 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.scripting.defaults;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeException;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

/**
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class DefaultParameterHandler implements ParameterHandler {

  /**
   * TypeHandlerRegistry 对象，管理 MyBat 工 s 中的 全部 TypeHandler 对象
   */
  private final TypeHandlerRegistry typeHandlerRegistry;

  /**
   * MappedStatement 对象，其中记录 SQL 节点相应的配置信息
   */
  private final MappedStatement mappedStatement;

  /**
   * 用户传入的实参对象
   */
  private final Object parameterObject;

  /**
   * 对应的 BoundSql 对象，需要设置参数的 PreparedStatement 对象 就是根据该 BoundSql 记录的 SQL
   * 语句创建的， BoundSql 中也记录了对应参数的名称和相关属性
   */
  private final BoundSql boundSql;
  private final Configuration configuration;

  public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    this.mappedStatement = mappedStatement;
    this.configuration = mappedStatement.getConfiguration();
    this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
    this.parameterObject = parameterObject;
    this.boundSql = boundSql;
  }

  @Override
  public Object getParameterObject() {
    return parameterObject;
  }

  /**
   * 遍历 BoundSql.parameterMappings 集合
   * 中记录的 ParameterMapping 对象，井根据其中记录的参数名称查找相应实参，然后与 SQL 语句
   * 绑定
   */
  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 1. 获取 对象参数映射列表
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    // 2. 检测 rameterMapping 集合是否为空
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        // 3. 过滤掉存储过程中的输出参数
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          // 4. 记录绑定的实参
          Object value;
          // 5. 获取参数名称
          String propertyName = parameterMapping.getProperty();
          // issue #448 ask first for additional params
          // 6. 获取对应的实参值
          if (boundSql.hasAdditionalParameter(propertyName)) {
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            // 7. 整个实参为空
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            // 8. 实参可以直接通过 TypeHandler 转换成 JdbcType
            value = parameterObject;
          } else {
            // 9、此处是对复杂对象的取值，首先将复杂对象（包含多个属性）封装成 MetaObject 在通过 metaObject.getValue 从对象中获取属性
            // 获取对象中相应的属性位或查找 Map 对象中位
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }

          // 10. 获取 ParameterMapping 中设置的 TypeHandler 对象
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          // jdbcType -> name: VARCHAR
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            // 通过 TypeHandler.setParameter() 方法会调用 PreparedStatement.set*() 方法
            // 为 SQL 语句绑定相应的实参
            // 为 SQL 语句绑定完实参之后，就可以调用 Statement 对象相应的 execute() 方法，将 SQL 语
            // 句交给数据库执行了
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

}
